Skip to content

Conversation

@isaric
Copy link

@isaric isaric commented Jan 6, 2026

https://issues.apache.org/jira/browse/SOLR-17600

Description

This pull request replaces the deprecated MapSerializable interface with MapWriter across the Solr codebase. MapSerializable relied on the toMap(Map) pattern, which often led to unnecessary object allocations and inconsistent serialization behavior. By migrating to MapWriter and its writeMap(EntryWriter) pattern, we improve memory efficiency and ensure more consistent serialization across different formats like JavaBin and JSON.

Solution

The migration involved several key steps:

  1. Interface Removal: Deleted the MapSerializable interface and removed its usage from the MapWriter interface. MapWriter now serves as the primary interface for object-to-map serialization.
  2. Refactoring Components: Updated numerous classes across the codebase to implement MapWriter instead of MapSerializable. Key areas include:
    • Core Configuration: SolrConfig, PluginInfo, SolrIndexConfig, CacheConfig, ConfigOverlay, RequestParams, IndexSchema.
    • Cloud & Admin APIs: Many V2 API response classes such as CreateCore, InstallShardData, MigrateDocsAPI, ModifyCollectionAPI, MoveReplicaAPI, SplitShardAPI, and more.
    • Internal State: ZkNodeProps, IndexFingerprint.
  3. Method Migration: Converted toMap(Map<String, Object> map) implementations to writeMap(EntryWriter ew). This allows for direct writing to the underlying data structure (like JavaBinCodec's output stream) without necessarily creating intermediate Map objects.
  4. Utility Updates: Updated Utils.convertToMap, Utils.reflectToMap, and other utility methods to support the new pattern. Utils.makeDeepCopy now uses SimpleOrderedMap with MapWriter for better efficiency.
  5. Clean up: Removed redundant toMap calls and simplified serialization logic in several handlers and components. Updated JavaBinCodec, TextWriter, and JSONWriter to optimize for MapWriter.

Tests

Existing tests were updated to accommodate the change in serialization:

  • Updated TestConfig, SolrIndexConfigTest, CacheConfigTest, and NodeConfigClusterPluginsSourceTest to verify that the configuration objects are still correctly serialized.
  • Updated TestSchemaDesignerAPI and SolrExampleCborTest to ensure API and serialization consistency.
  • Verified that JavaBinCodec and JSONWriter correctly handle the new MapWriter implementations.
  • Ran core Solr tests to ensure no regressions in response formatting or internal state representation.

Checklist

Please review the following and check all that apply:

  • I have reviewed the guidelines for How to Contribute and my code conforms to the standards described there to the best of my ability.
  • I have created a Jira issue and added the issue ID to my pull request title.
  • I have given Solr maintainers access to contribute to my PR branch. (optional but recommended, not available for branches on forks living under an organisation)
  • I have developed this patch against the main branch.
  • I have run ./gradlew check.
  • I have added tests for my changes.
  • I have added documentation for the Reference Guide
  • I have added a changelog entry for my change

isaric added 13 commits January 6, 2026 10:12
…Map` for consistency in plugin and handler serialization
…deredMap` for consistent serialization in config and handler classes
…plugin and component info serialization with `SimpleOrderedMap`
Map deepCopy = Utils.getDeepCopy(data, 3);
Map p = (Map) deepCopy.get(NAME);
if (p == null) deepCopy.put(NAME, p = new LinkedHashMap<>());
if (paramSet == null) p.remove(name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this if should have { } to match the else?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that in Java conditionals should always use curly braces for clarity. This is the existing code that was not changed in my PR.

I am hesitant to change it as I am not sure if there is a convention regarding this practice in the project.

"cacheControl",
cacheControlHeader);
public void writeMap(EntryWriter ew) throws IOException {
ew.put("never304", never304)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is nicer pattern, less a "unique magic pattern".


public static CreateCoreParams createRequestBodyFromV1Params(SolrParams solrParams) {
final var v1ParamMap = solrParams.toMap(new HashMap<>());
final var v1ParamMap = new SimpleOrderedMap<>(solrParams);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a subtle semantic difference here. Previously, we had a bonafide Map. If there were duplicate keys, the last value wins (looking at Utils.convertToMap, called by toMap). Probably wasn't intended but there is integrity of the Map contract at least. The constructor you use assumes the input has no duplicate keys. They will be added, and SimpleOrderedMap will not quite act exactly like a Map. I'm not sure what the final effect is without trying/testing. We could change SimpleOrderedMap's constructor here to have a hack for the NamedList (that isn't SimpleOrderedMap) case. Or don't use SimpleOrderedMap here, which I can see was chosen for your convenience, not because it's needed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced with Utils.convertToMap.

…CreateCore` for parameter handling simplification
Copy link
Contributor

@dsmiley dsmiley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from just removing a redundant abstraction, this change should have the benefit of removing intermediate needless data structures (Maps/NamedList/SimpleOrderedMap). But I'm seeing here a continuation of that.

The only place we need the data structure is where toMap was being called. Perhaps it'd be ideal to split this huge PR in two -- first address toMap callers, then the rest.

Comment on lines 82 to 83
Map<String, Object> config = p.initArgs.toMap(new HashMap<>());
Map<String, Object> config = new SimpleOrderedMap<>(p.initArgs);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, avoid NamedList/SimpleOrderedMap

How about p.initArgs.asMap(0)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed as requested

CoreAdminParams.ACTION, CoreAdminParams.CoreAdminAction.INSTALLCOREDATA.toString());
typedMessage.toMap(new HashMap<>()).forEach((k, v) -> coreApiParams.set(k, v.toString()));

new SimpleOrderedMap<>(typedMessage).forEach((k, v) -> coreApiParams.set(k, v.toString()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typedMessage._forEachEntry here instead
(avoiding senseless intermediate data structure creation).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

Comment on lines +200 to +203
new NamedList<>(attributes).writeMap(ew);
if (initArgs != null) {
new SimpleOrderedMap<>(initArgs).writeMap(ew);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not create a new Map just to ultimately write it!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed caused test failures. Unsure why. Error was cannot cast LinkedHashMap to String however stack trace was unclear as to location of exception.
Reverted change.

if (paramSet == null) p.remove(name);
else p.put(name, paramSet.toMap(new LinkedHashMap<>()));
else {
p.put(name, new SimpleOrderedMap<>(paramSet));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one's fine

isaric and others added 2 commits January 9, 2026 15:57
@isaric
Copy link
Author

isaric commented Jan 9, 2026

@dsmiley I followed the advice in the comments however I had test failures in solr:core after pushing changes. Reverted changes to PluginInfo and tests are passing on my Codespaces instance. I don't think the solr:core tests ran for the last commit. I guess it is restricted due to heavy resource use.

@@ -0,0 +1,7 @@
title: SOLR 17600 - Replace MapSerializable with MapWriter
type: changed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactorings are "other".
I'm not happy with the logchange plugin constraining the enum choices here

Comment on lines +994 to +999
ew.put(
tag,
new SimpleOrderedMap<>(
m -> {
new NamedList<>(items).writeMap(m);
}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what I'm looking at here


messageTyped.validate();
return new ZkNodeProps(messageTyped.toMap(new HashMap<>()));
return new ZkNodeProps((Map<String, Object>) new SimpleOrderedMap<>(messageTyped));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally, we should avoid creating a SimpleOrderedMap. Use Utils.convertToMap instead.
A SimpleOrderedMap is okay to add to a MapWritable entry though, or any similar place for a response-writing use-case. A SimpleOrderedMap does not hash its data, so has O(N) access. It's designed for writing out data that will unlikely have its keys looked up. It's not a general replacement for a Map.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants